// Blurer.cpp: implementation of the CBlurer class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "Blurer.h"

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CBlurer::CBlurer( PDIRECT3DDEVICE8 pDevice, BOOL useZBuffer, DWORD width, DWORD height, D3DFORMAT format, D3DFORMAT zformat )
{
	if( !pDevice )
		throw CSystemException( "CBlurer: null pointer device" );

	m_pDevice = pDevice;

	D3DVIEWPORT8			vp;
	PTLVERTEX				pVertices;
	DWORD					dwClearFlag = D3DCLEAR_TARGET;

	if( FAILED( pDevice->GetViewport( &vp ) ) )
		throw CSystemException( "CBlurer: unable to create vertex buffer" );

	if( FAILED( pDevice->CreateVertexBuffer( 6*sizeof(TLVERTEX), 0, FVF_TLVERTEX, D3DPOOL_DEFAULT, &m_pvbBlurQuad ) ) )
		throw CSystemException( "CBlurer: unable to create blur quad vertex buffer" );

	if( FAILED( pDevice->CreateVertexBuffer( 6*sizeof(TLVERTEX), 0, FVF_TLVERTEX, D3DPOOL_DEFAULT, &m_pvbOutQuad ) ) )
	{	
		m_pvbBlurQuad->Release();
		throw CSystemException( "CBlurer: unable to create out quad vertex buffer" );
	}

	m_pvbOutQuad->Lock( 0, 0, (LPBYTE*)&pVertices, 0 );

	*(pVertices++) = TLVERTEX( (FLOAT)vp.X - 1.0f, (FLOAT)vp.Y - 1.0f, 0.0f, 0.5f, 0x80ffffff, 0.0f, 0.0f );
	*(pVertices++) = TLVERTEX( (FLOAT)(vp.X + vp.Width), (FLOAT)vp.Y - 1.0f, 0.0f, 0.5f, 0x80ffffff, 1.0f, 0.0f );
	*(pVertices++) = TLVERTEX( (FLOAT)vp.X - 1.0f, (FLOAT)(vp.Y + vp.Height), 0.0f, 0.5f, 0x80ffffff, 0.0f, 1.0f );

	*(pVertices++) = TLVERTEX( (FLOAT)vp.X - 1.0f, (FLOAT)(vp.Y + vp.Height), 0.0f, 0.5f, 0x80ffffff, 0.0f, 1.0f );
	*(pVertices++) = TLVERTEX( (FLOAT)(vp.X + vp.Width), (FLOAT)vp.Y - 1.0f, 0.0f, 0.5f, 0x80ffffff, 1.0f, 0.0f );
	*(pVertices++) = TLVERTEX( (FLOAT)(vp.X + vp.Width), (FLOAT)(vp.Y + vp.Height), 0.0f, 0.5f, 0x80ffffff, 1.0f, 1.0f );	

	m_pvbOutQuad->Unlock();	

	m_pvbBlurQuad->Lock( 0, 0, (LPBYTE*)&pVertices, 0 );

	*(pVertices++) = TLVERTEX( -5.0f, -5.0f, 0.0f, 0.5f, 0x80ffffff, 0.0f, 0.0f );
	*(pVertices++) = TLVERTEX( (FLOAT)(width) + 5, -5.0f, 0.0f, 0.5f, 0x80ffffff, 1.0f, 0.0f );
	*(pVertices++) = TLVERTEX( -5.0f, (FLOAT)(height) + 5, 0.0f, 0.5f, 0x80ffffff, 0.0f, 1.0f );

	*(pVertices++) = TLVERTEX( (FLOAT)-5.0f, (FLOAT)(height) + 5, 0.0f, 0.5f, 0x80ffffff, 0.0f, 1.0f );
	*(pVertices++) = TLVERTEX( (FLOAT)(width) + 5, (FLOAT)-5.0f, 0.0f, 0.5f, 0x80ffffff, 1.0f, 0.0f );
	*(pVertices++) = TLVERTEX( (FLOAT)(width) + 5, (FLOAT)(height) + 5, 0.0f, 0.5f, 0x80ffffff, 1.0f, 1.0f );	

	m_pvbBlurQuad->Unlock();	

	m_ptextTargets[0] = NULL;
	m_ptextTargets[1] = NULL;
	m_psurfTargetZbuffer = NULL;
	m_psurfPrevTarget = NULL;
	m_psurfPrevZbuffer = NULL;	

	m_dwCurrentPage = 0;
	m_bBeginScene = FALSE;	
	m_dwAlpha = 0x80;	

	if( FAILED( m_pDevice->CreateTexture( width, height, 1, D3DUSAGE_RENDERTARGET, format, D3DPOOL_DEFAULT, &m_ptextTargets[0] ) ) )	
		throw CSystemException( "CBlurer: unable to create texture target [0]" );	

	if( FAILED( m_pDevice->CreateTexture( width, height, 1, D3DUSAGE_RENDERTARGET, format, D3DPOOL_DEFAULT, &m_ptextTargets[1] ) ) )
		throw CSystemException( "CBlurer: unable to create texture target [1]" );	

	if( useZBuffer )
	{		
		if( FAILED( m_pDevice->CreateDepthStencilSurface( width, height, zformat, D3DMULTISAMPLE_NONE, &m_psurfTargetZbuffer ) ) )
			throw CSystemException( "CBlurer: unable to create zbuffer for target surface" );
	}			

	if( !Clear() )
		throw CSystemException( "CBlurer: unable to clear targets surfaces" );
}

CBlurer::~CBlurer()
{
	if( m_bBeginScene )	
		throw CSystemException( "CBlurer: trying to free target texture when still assigned to device" );
		
	if( m_ptextTargets[0] )
		m_ptextTargets[0]->Release();

	if( m_ptextTargets[1] )
		m_ptextTargets[1]->Release();

	if( m_psurfTargetZbuffer)
		m_psurfTargetZbuffer->Release();
	
	if( m_pvbOutQuad )
		m_pvbOutQuad->Release();

	if( m_pvbBlurQuad )
		m_pvbBlurQuad->Release();
}

BOOL CBlurer::BeginScene()
{		
	if( m_bBeginScene )
		return TRUE;

	LPDIRECT3DSURFACE8		psurf;	

	if( FAILED( m_ptextTargets[m_dwCurrentPage]->GetSurfaceLevel( 0, &psurf ) ) )
		return FALSE;		

	if( FAILED( m_pDevice->GetRenderTarget( &m_psurfPrevTarget ) ) )
		return FALSE;	

	if( FAILED( m_pDevice->GetDepthStencilSurface( &m_psurfPrevZbuffer ) ) )
		return FALSE;	

	if( FAILED( m_pDevice->SetRenderTarget( psurf, m_psurfTargetZbuffer ) ) )
		return FALSE;		

	psurf->Release();
	m_psurfPrevTarget->Release();	

	if( m_psurfPrevZbuffer )
		m_psurfPrevZbuffer->Release();
	
	m_bBeginScene = TRUE;
	m_dwCurrentPage ^= 0x1;

	if( FAILED( m_pDevice->BeginScene() ) )
		return FALSE;

	return TRUE;
}

BOOL CBlurer::EndScene()
{
	if( !m_bBeginScene )
		return TRUE;	

	DWORD					dwAlphaEnable;
	DWORD					dwSrcBlend;
	DWORD					dwDestBlend;	
	DWORD					dwAlphaOp;

	m_pDevice->GetRenderState( D3DRS_ALPHABLENDENABLE, &dwAlphaEnable );
	m_pDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE );
	m_pDevice->GetRenderState( D3DRS_SRCBLEND, &dwSrcBlend );
	m_pDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA );
	m_pDevice->GetRenderState( D3DRS_DESTBLEND, &dwDestBlend );
	m_pDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA );

	m_pDevice->SetTexture( 0, m_ptextTargets[m_dwCurrentPage] );
	m_pDevice->GetTextureStageState( 0, D3DTSS_ALPHAOP, &dwAlphaOp );
	m_pDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2 );

	m_pDevice->SetVertexShader( FVF_TLVERTEX );
	m_pDevice->SetStreamSource( 0, m_pvbBlurQuad, sizeof(TLVERTEX) );
	m_pDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, 2 );

	m_pDevice->EndScene();

	if( FAILED( m_pDevice->SetRenderTarget( m_psurfPrevTarget, m_psurfPrevZbuffer ) ) )
		return FALSE;	
	
	m_pDevice->BeginScene();

	m_pDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE );

	m_pDevice->SetTexture( 0, m_ptextTargets[m_dwCurrentPage^1] );

	m_pDevice->SetVertexShader( FVF_TLVERTEX );
	m_pDevice->SetStreamSource( 0, m_pvbOutQuad, sizeof(TLVERTEX) );
	m_pDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, 2 );

	m_pDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, dwAlphaEnable );	
	m_pDevice->SetRenderState( D3DRS_SRCBLEND, dwSrcBlend );	
	m_pDevice->SetRenderState( D3DRS_DESTBLEND, dwDestBlend );	
	
	m_pDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, dwAlphaOp );

	m_pDevice->EndScene();

	m_bBeginScene = FALSE;

	return TRUE;
}

PDIRECT3DTEXTURE8 CBlurer::GetCurrentTargetTexture()
{
	return m_ptextTargets[m_dwCurrentPage];
}

PDIRECT3DTEXTURE8 CBlurer::GetPrevTargetTexture()
{
	return m_ptextTargets[m_dwCurrentPage^1];
}

PDIRECT3DTEXTURE8 CBlurer::GetTargetTexture( DWORD dwPage )
{
	return m_ptextTargets[dwPage&1];
}

BOOL CBlurer::SetBlurFactor( FLOAT fFactor )
{
	DWORD					dwFactor = (DWORD)(fFactor*255.0f);
	DWORD					dwColor;

	if( dwFactor > 0xff )
		dwFactor = 0xff;	

	if( dwFactor == m_dwAlpha )
		return TRUE;
	
	m_dwAlpha = dwFactor;
	dwColor = ( dwFactor << 24 ) | 0xffffff;

	PTLVERTEX				pVertices;

	m_pvbBlurQuad->Lock( 0, 0, (LPBYTE*)&pVertices, 0 );

	pVertices[0].m_dwColor = dwColor;
	pVertices[1].m_dwColor = dwColor;
	pVertices[2].m_dwColor = dwColor;
	pVertices[3].m_dwColor = dwColor;
	pVertices[4].m_dwColor = dwColor;
	pVertices[5].m_dwColor = dwColor;

	m_pvbBlurQuad->Unlock();

	return TRUE;
}

BOOL CBlurer::SetBlurFactor( DWORD dwFactor )
{
	DWORD					dwColor;

	if( dwFactor > 0xff )
		dwFactor = 0xff;

	if( dwFactor == m_dwAlpha )
		return TRUE;
	
	m_dwAlpha = dwFactor;
	dwColor = ( dwFactor << 24 ) | 0xffffff;

	PTLVERTEX				pVertices;

	m_pvbBlurQuad->Lock( 0, 0, (LPBYTE*)&pVertices, 0 );

	pVertices[0].m_dwColor = dwColor;
	pVertices[1].m_dwColor = dwColor;
	pVertices[2].m_dwColor = dwColor;
	pVertices[3].m_dwColor = dwColor;
	pVertices[4].m_dwColor = dwColor;
	pVertices[5].m_dwColor = dwColor;

	m_pvbBlurQuad->Unlock();

	return TRUE;
}

BOOL CBlurer::Clear()
{
	LPDIRECT3DSURFACE8		psurf;	
	DWORD					dwClearFlags = D3DCLEAR_TARGET;

	if( m_psurfTargetZbuffer )
		dwClearFlags |= D3DCLEAR_ZBUFFER;

	if( FAILED( m_ptextTargets[m_dwCurrentPage]->GetSurfaceLevel( 0, &psurf ) ) )
		return FALSE;			

	if( FAILED( m_pDevice->GetRenderTarget( &m_psurfPrevTarget ) ) )
		return FALSE;	

	if( FAILED( m_pDevice->GetDepthStencilSurface( &m_psurfPrevZbuffer ) ) )
		return FALSE;	

	if( FAILED( m_pDevice->SetRenderTarget( psurf, m_psurfTargetZbuffer ) ) )
		return FALSE;		

	psurf->Release();
	m_psurfPrevTarget->Release();
	if( m_psurfPrevZbuffer )
		m_psurfPrevZbuffer->Release();
	

	if( FAILED( m_pDevice->Clear( 0, NULL, dwClearFlags, 0x00, 1.0f, 0 ) ) )
		return FALSE;

	if( FAILED( m_ptextTargets[m_dwCurrentPage^1]->GetSurfaceLevel( 0, &psurf ) ) )
		return FALSE;	

	if( FAILED( m_pDevice->SetRenderTarget( psurf, m_psurfTargetZbuffer ) ) )
		return FALSE;	
	
	psurf->Release();

	if( FAILED( m_pDevice->Clear( 0, NULL, dwClearFlags, 0x00, 1.0f, 0 ) ) )
		return FALSE;
	
	if( FAILED( m_pDevice->SetRenderTarget( m_psurfPrevTarget, m_psurfPrevZbuffer ) ) )
		return FALSE;		

	return TRUE;
}

BOOL CBlurer::SetTarget( DWORD dwIndex )
{
	LPDIRECT3DSURFACE8		psurf;	

	if( FAILED( m_ptextTargets[dwIndex]->GetSurfaceLevel( 0, &psurf ) ) )
		return FALSE;		

	if( FAILED( m_pDevice->GetRenderTarget( &m_psurfPrevTarget ) ) )
		return FALSE;	

	if( FAILED( m_pDevice->GetDepthStencilSurface( &m_psurfPrevZbuffer ) ) )
		return FALSE;	

	if( FAILED( m_pDevice->SetRenderTarget( psurf, m_psurfTargetZbuffer ) ) )
		return FALSE;		

	psurf->Release();
	m_psurfPrevTarget->Release();	

	if( m_psurfPrevZbuffer )
		m_psurfPrevZbuffer->Release();

	return TRUE;
}

BOOL CBlurer::RestoreTarget()
{
	if( FAILED( m_pDevice->SetRenderTarget( m_psurfPrevTarget, m_psurfPrevZbuffer ) ) )
		return FALSE;	

	return TRUE;
}